//
//	File: OyDirect3D9_enum.cpp
//
#include "OyDirect3D9_enum.h"
#include "OyDirect3D9.h"
#include <tchar.h>

// some common backbuffer formats
const UINT g_nFormats_B = 9;
const D3DFORMAT g_fmtBackBuffer[] = {
           D3DFMT_R8G8B8,   D3DFMT_A8R8G8B8, 
           D3DFMT_X8R8G8B8, D3DFMT_R5G6B5, 
           D3DFMT_A1R5G5B5, D3DFMT_X1R5G5B5,
           D3DFMT_X4R4G4B4, D3DFMT_A4R4G4B4,
           D3DFMT_A2B10G10R10 
           };


// some good multisample modes, list best ones last
const UINT g_nMS = 8;
const D3DMULTISAMPLE_TYPE g_msType[] = { 
           D3DMULTISAMPLE_NONE,
           D3DMULTISAMPLE_2_SAMPLES,
           D3DMULTISAMPLE_3_SAMPLES,
           D3DMULTISAMPLE_4_SAMPLES,
           D3DMULTISAMPLE_5_SAMPLES,
           D3DMULTISAMPLE_6_SAMPLES,
           D3DMULTISAMPLE_7_SAMPLES,
           D3DMULTISAMPLE_8_SAMPLES,
           };

static int __cdecl SortModesCallback(const void* arg1, const void* arg2) 
{
	D3DDISPLAYMODE* pdm1 = (D3DDISPLAYMODE*)arg1;
	D3DDISPLAYMODE* pdm2 = (D3DDISPLAYMODE*)arg2;
   
	if (pdm1->Width > pdm2->Width)
		return 1;
	if (pdm1->Width < pdm2->Width)
		return -1;
	if (pdm1->Height > pdm2->Height)
		return 1;
	if (pdm1->Height < pdm2->Height)
		return -1;
	if (pdm1->Format > pdm2->Format)
		return 1;
	if (pdm1->Format < pdm2->Format)
		return -1;
	if (pdm1->RefreshRate > pdm2->RefreshRate)
		return 1;
	if (pdm1->RefreshRate < pdm2->RefreshRate)
		return -1;

	return 0;
}


HRESULT OyDirect3D9Enum::Enum(void) 
{
	HRESULT hr;

	// create main Direct3D object
	m_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	if( m_pD3D == NULL )
	{
		return OY_CREATEAPI;
	}

	// nothing yet
	m_dwNumAdapters = 0;

	// set default values
	m_nMinWidth  = 800;
	m_nMinHeight = 600;
	m_nMinBits   = 16;
   
	// set allowed bpp formats
	m_fmtAdapter[0] = D3DFMT_X8R8G8B8;  // 32 Bit
	m_fmtAdapter[1] = D3DFMT_X1R5G5B5;  // 15 Bit
	m_fmtAdapter[2] = D3DFMT_R5G6B5;    // 16 Bit
	m_nNumFmt = 3;

	// get current adapters display mode
	if (FAILED(m_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &m_dspmd))) 
	{
		return OY_FAIL;
	}

	// enumerate available adapters
	hr = EnumAdapters();
   
	// we don't need this for now
	m_pD3D->Release();
	m_pD3D = NULL;

	if( FAILED(hr) )
		return OY_FAIL;
/*
   for (UINT i=0; i<m_dwNumAdapters; i++) 
   {
      AddItem(m_hADAPTER, m_xAdapterInfo[a].
              d3dAdapterIdentifier.Description, 
              &m_xAdapterInfo[a]);
      }

   // treat enum like adapter change
   SendMessage(m_hADAPTER, CB_SETCURSEL, (WPARAM)0, 0);
   ChangedAdapter();

   // select first entry from each combo
   SendMessage(m_hADAPTER, CB_SETCURSEL, (WPARAM)0, 0);
   SendMessage(m_hMODE,    CB_SETCURSEL, (WPARAM)0, 0);
   SendMessage(m_hDEVICE,  CB_SETCURSEL, (WPARAM)0, 0);
   SendMessage(m_hBACKFMT, CB_SETCURSEL, (WPARAM)0, 0);
   SendMessage(m_hADAPTERFMT, CB_SETCURSEL, (WPARAM)0, 0);
*/
	return OY_OK;
}

HRESULT OyDirect3D9Enum::EnumAdapters(void) 
{
	D3DDISPLAYMODE d3ddspmd;
	HRESULT        hr;
	UINT           nNumAdapters=0;
	UINT           nNumModes=0;

	if( !m_pD3D ) 
		return OY_FAIL;

	// get number of attached adapters
	nNumAdapters = m_pD3D->GetAdapterCount();

	// loop through adapters
	for(UINT nAdapter=0; nAdapter<nNumAdapters; nAdapter++) 
	{
		m_xAdapterInfo[m_dwNumAdapters].nAdapter  = nAdapter;
		m_xAdapterInfo[m_dwNumAdapters].nNumModes = 0;

		// get adapter information struct
		m_pD3D->GetAdapterIdentifier(nAdapter, 0, 
									&m_xAdapterInfo[m_dwNumAdapters].
									d3dAdapterIdentifier);

		// loop through allowed bpp formats
		for(UINT nFmt=0; nFmt<m_nNumFmt; nFmt++) 
		{
			// get available display modes for this adapter/bpp format
			nNumModes = m_pD3D->GetAdapterModeCount(nAdapter, m_fmtAdapter[nFmt]);
         
			for(UINT nMode=0; nMode<nNumModes; nMode++) 
			{
				// get number of available display modes
				m_pD3D->EnumAdapterModes(nAdapter, m_fmtAdapter[nFmt], nMode, &d3ddspmd);

				// don't use less than we asked for
				if( d3ddspmd.Width < m_nMinWidth || d3ddspmd.Height < m_nMinHeight ||
					GetBits(d3ddspmd.Format) < m_nMinBits ) 
				{
					continue;
				}
				else 
				{
					m_xAdapterInfo[m_dwNumAdapters].d3ddspmd[m_xAdapterInfo[m_dwNumAdapters].nNumModes] = d3ddspmd;
					m_xAdapterInfo[m_dwNumAdapters].nNumModes++;
				}
			} // for [modes]
		} // for [formats]

		// we got the data, now sort it
		qsort(	m_xAdapterInfo[m_dwNumAdapters].d3ddspmd, 
				m_xAdapterInfo[m_dwNumAdapters].nNumModes,
				sizeof(D3DDISPLAYMODE),
				SortModesCallback );

		if( FAILED( hr = EnumDevices(m_xAdapterInfo[m_dwNumAdapters]) ) ) 
			return hr;

		// keep this one if any device is found
		if( m_xAdapterInfo[m_dwNumAdapters].nNumDevs > 0 )
			m_dwNumAdapters++;

	} // for [adapters]

	return OY_OK;
}


HRESULT OyDirect3D9Enum::EnumDevices(OyADAPTERINFO_D3DDEVICE &xAdapter) 
{
	OyDEVICEINFO_D3DDEVICE	*pDev;
	HRESULT					hr;

	const D3DDEVTYPE d3dDevTypes[] = { 
				D3DDEVTYPE_HAL, 
				D3DDEVTYPE_SW, 
				D3DDEVTYPE_REF 
				};

	xAdapter.nNumDevs = 0;

	for(UINT nDev=0; nDev<3; nDev++) 
	{
		pDev = &xAdapter.d3dDevs[xAdapter.nNumDevs];

		pDev->nNumCombo   = 0;
		pDev->nAdapter    = xAdapter.nAdapter;
		pDev->d3dDevType  = d3dDevTypes[nDev];

		// will fail if unsupported devtype
		if( FAILED(m_pD3D->GetDeviceCaps(pDev->nAdapter,
					pDev->d3dDevType, &pDev->d3dCaps)) )
			continue;

		// this should not fail
		if( FAILED(hr=EnumCombos(*pDev)) )
			return hr;
      
		// at least one supportet combo?
		if( pDev->nNumCombo <= 0 )
			continue;

		// keep valid device
		xAdapter.nNumDevs++;
	} // for

   return OY_OK;
}

HRESULT OyDirect3D9Enum::EnumCombos(OyDEVICEINFO_D3DDEVICE &xDev) 
{
	OyCOMBOINFO_D3DDEVICE *pCombo;
	bool bWindowed;
	bool bFmtCheck = false;

	xDev.nNumCombo = 0;

	// all adapter Formats
	for( UINT nFmt_A=0; nFmt_A<m_nNumFmt; nFmt_A++ )
	{
		// for all allowed backbuffer formats
		for( UINT nFmt_B=0; nFmt_B<g_nFormats_B; nFmt_B++ )
		{
			// check windowed and fullscreen
			for( UINT n=0; n<2; n++ ) 
			{
				if( n==0 )
					bWindowed = true;
				else 
					bWindowed = false;

				// valid combo?
				if( FAILED(m_pD3D->CheckDeviceType(
							xDev.nAdapter, 
							xDev.d3dDevType, 
							m_fmtAdapter[nFmt_A], 
							g_fmtBackBuffer[nFmt_B], 
							bWindowed)) )
					continue;

				// at this point we have a valid combo that is supported
				// by the system. still need to find depth/stencil fmt
				// and best vertex processing type. 
				pCombo = &xDev.d3dCombo[xDev.nNumCombo];
				pCombo->nAdapter      = xDev.nAdapter;
				pCombo->d3dDevType    = xDev.d3dDevType;
				pCombo->bWindowed     = bWindowed;
				pCombo->fmtBackBuffer = g_fmtBackBuffer[nFmt_B];
				pCombo->fmtAdapter    = m_fmtAdapter[nFmt_A];

				// select depth fmt & stencil buffer
				if( m_nMinStencil ) 
				{
					pCombo->fmtDepthStencil = D3DFMT_D24S8;
					bFmtCheck = ConfirmDepthFmt(pCombo);
              
					if(!bFmtCheck) 
					{
						pCombo->fmtDepthStencil = D3DFMT_D24X4S4;
						bFmtCheck = ConfirmDepthFmt(pCombo);
					}
					if(!bFmtCheck)
					{
						pCombo->fmtDepthStencil = D3DFMT_D15S1;
						bFmtCheck = ConfirmDepthFmt(pCombo);
					}
				}
				else
				{
					if( m_nMinDepth > 24 )
					{
						pCombo->fmtDepthStencil = D3DFMT_D32;
						bFmtCheck = ConfirmDepthFmt(pCombo);
					}
					if( !bFmtCheck && (m_nMinDepth > 16) )
					{
						pCombo->fmtDepthStencil = D3DFMT_D24X8;
						bFmtCheck = ConfirmDepthFmt(pCombo);
					}
					else 
					{
						pCombo->fmtDepthStencil = D3DFMT_D16;
						bFmtCheck = ConfirmDepthFmt(pCombo);
					}
				}

				// no suitable fmt => ignore this combo
				if( !bFmtCheck ) 
					continue;

				// we have hardware T&L
				if( (xDev.d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) != 0 ) 
				{               
					// 1.case: pure device
					if ( ((xDev.d3dCaps.DevCaps & D3DDEVCAPS_PUREDEVICE) != 0 ) &&
						 (ConfirmDevice(&xDev.d3dCaps, D3DCREATE_PUREDEVICE, pCombo->fmtBackBuffer) == OY_OK) )
					{
						pCombo->dwBehavior = D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE;
					}
					// 2.case: hardware device
					else if( ConfirmDevice(&xDev.d3dCaps, D3DCREATE_HARDWARE_VERTEXPROCESSING, pCombo->fmtBackBuffer) == OY_OK ) 
					{
						pCombo->dwBehavior = D3DCREATE_HARDWARE_VERTEXPROCESSING;
					}
					// 3.case: mixed sw/hw
					else if( ConfirmDevice(&xDev.d3dCaps, D3DCREATE_MIXED_VERTEXPROCESSING, pCombo->fmtBackBuffer) == OY_OK )
					{
						pCombo->dwBehavior = D3DCREATE_MIXED_VERTEXPROCESSING;
					}
				} // if [HW]
				// 4.case: must be sw processing
				else 
				{
					if( ConfirmDevice(&xDev.d3dCaps, D3DCREATE_SOFTWARE_VERTEXPROCESSING, pCombo->fmtBackBuffer) == OY_OK ) 
					{
						pCombo->dwBehavior = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
					}
				}

				// SELECT MULTISAMPLE TYPE
				DWORD msQuality;
				for( UINT nMS=g_nMS-1; nMS>=0; nMS--) 
				{
					if( SUCCEEDED(m_pD3D->CheckDeviceMultiSampleType(
												pCombo->nAdapter, 
												pCombo->d3dDevType, 
												pCombo->fmtBackBuffer, 
												pCombo->bWindowed,
												g_msType[nMS], &msQuality)) ) 
					{
						pCombo->msType = g_msType[nMS];
						break;
					}
				} // for [multisample]

				// SHADER VERSION
				pCombo->dwVertexShaderVer = xDev.d3dCaps.VertexShaderVersion;
				pCombo->dwPixelShaderVer = xDev.d3dCaps.PixelShaderVersion;
				pCombo->dwVertexShaderConst = xDev.d3dCaps.MaxVertexShaderConst;

				// NO WE GOT EVERYTHING WE NEE AND KEEP THIS COMBO
				xDev.nNumCombo++;

			} // for [windowed/fullscreen]
		} // for [BackbufferFormats]
	} // for [AdapterFormats]

   return OY_OK;
}

bool OyDirect3D9Enum::ConfirmDepthFmt(OyCOMBOINFO_D3DDEVICE *pCombo)
{
   // compatible with adapter?
	if( FAILED(m_pD3D->CheckDeviceFormat(	pCombo->nAdapter, 
											pCombo->d3dDevType, 
											pCombo->fmtAdapter, 
											D3DUSAGE_DEPTHSTENCIL, 
											D3DRTYPE_SURFACE, 
											pCombo->fmtDepthStencil)) )
		return false;

	// compatible with backbuffer format?
	if( FAILED(m_pD3D->CheckDepthStencilMatch(	pCombo->nAdapter, 
												pCombo->d3dDevType, 
												pCombo->fmtAdapter, 
												pCombo->fmtBackBuffer,
												pCombo->fmtDepthStencil)) )
		return false;

	return true;
}

HRESULT OyDirect3D9Enum::ConfirmDevice(D3DCAPS9* pCaps, DWORD dwBehavior, D3DFORMAT fmtBackbuffer) 
{
	if( (dwBehavior & D3DCREATE_HARDWARE_VERTEXPROCESSING) ||
		(dwBehavior & D3DCREATE_MIXED_VERTEXPROCESSING) ) 
	{
		// alphablending from texture pixels supported
		if( !(pCaps->TextureCaps & D3DPTEXTURECAPS_ALPHA) )
		{
			OyLog_Write_L1(OyXMLLog::Error, OyXMLLog::Engine, L"[D3D_ENUM] error: no alphablending from texture");
			return OY_FAIL;
		}

		if( pCaps->VertexShaderVersion < D3DVS_VERSION(1,0) ) 
		{
			OyLog_Write_L1(OyXMLLog::Error, OyXMLLog::Engine, L"[D3D_ENUM] error: Vertex Shader Version < 1.0");
			return OY_FAIL;
		}
	}
 
	return OY_OK;
}

UINT OyDirect3D9Enum::GetBits(D3DFORMAT fmt) 
{
	switch(fmt) 
	{
		case D3DFMT_A2B10G10R10: 
			return 30;
		case D3DFMT_R8G8B8:      
			return 24;
		case D3DFMT_A8R8G8B8:    
			return 24;
		case D3DFMT_X8R8G8B8:    
			return 24;
		case D3DFMT_R5G6B5:      
			return 16;
		case D3DFMT_X1R5G5B5:    
			return 15;
		case D3DFMT_A1R5G5B5:    
			return 15;
		case D3DFMT_X4R4G4B4:    
			return 12;
		case D3DFMT_A4R4G4B4:    
			return 12;
		case D3DFMT_R3G3B2:      
			return 8;
		case D3DFMT_A8R3G3B2:    
			return 8;
		default:                 
			return 0;
	}
}

BOOL OyDirect3D9Enum::LoadD3DSetting(OyDEVICEINFO_D3DDEVICE *pD, D3DDISPLAYMODE *dspmd,
			D3DFORMAT *fmtA, D3DFORMAT *fmtB, bool *bWindowed, D3DMULTISAMPLE_TYPE *dwMultiSample)
{
	TCHAR IniStr[256];

	FILE *pf;
	errno_t err;

	if( err = fopen_s( &pf, "D3DSetting.ini", "r" ) )
	{
		if( pf )
			fclose( pf );
		return FALSE;
	}

	fclose( pf );

	if( ::GetPrivateProfileSection( L"D3DSetting", IniStr, 256, L".\\D3DSetting.ini" ) )
	{
		if( ::GetPrivateProfileString( L"D3DSetting", L"windowed", L"true", IniStr, 256, L".\\D3DSetting.ini" ) )
		{
			if( _tcsicmp( IniStr, L"true" ) == 0 )
				*bWindowed = true;
			else
				*bWindowed = false;
		}

		if( ::GetPrivateProfileString( L"D3DSetting", L"devicetype", L"HAL", IniStr, 256, L".\\D3DSetting.ini" ) )
		{
			if( _tcsicmp( IniStr, L"HAL" ) == 0 )
				pD->d3dDevType = D3DDEVTYPE_HAL;
			else if( _tcsicmp( IniStr, L"REF" ) == 0 )
				pD->d3dDevType = D3DDEVTYPE_REF;
			else if( _tcsicmp( IniStr, L"SW" ) == 0 )
				pD->d3dDevType = D3DDEVTYPE_SW;
		}

		if( ::GetPrivateProfileString( L"D3DSetting", L"adapterformat", L"A8R8G8B8", IniStr, 256, L".\\D3DSetting.ini" ) )
		{
			if( _tcsicmp( IniStr, L"A8R8G8B8" ) == 0 )
				*fmtA = D3DFMT_A8R8G8B8;
			else if( _tcsicmp( IniStr, L"R8G8B8" ) == 0 )
				*fmtA = D3DFMT_R8G8B8;
			else if( _tcsicmp( IniStr, L"X8R8G8B8" ) == 0 )
				*fmtA = D3DFMT_X8R8G8B8;
		}

		if( ::GetPrivateProfileString( L"D3DSetting", L"backbufferformat", L"A8R8G8B8", IniStr, 256, L".\\D3DSetting.ini" ) )
		{
			if( _tcsicmp( IniStr, L"A8R8G8B8" ) == 0 )
			{
				*fmtB = D3DFMT_A8R8G8B8;
			}
			else if( _tcsicmp( IniStr, L"R8G8B8" ) == 0 )
			{
				*fmtB = D3DFMT_R8G8B8;
			}
			else if( _tcsicmp( IniStr, L"X8R8G8B8" ) == 0 )
			{
				*fmtB = D3DFMT_X8R8G8B8;
			}
			dspmd->Format = *fmtB;
		}

		if( ::GetPrivateProfileString( L"D3DSetting", L"width", L"1024", IniStr, 256, L".\\D3DSetting.ini" ) )
		{
			dspmd->Width = _tstoi(IniStr);
		}
		if( ::GetPrivateProfileString( L"D3DSetting", L"height", L"768", IniStr, 256, L".\\D3DSetting.ini" ) )
		{
			dspmd->Height = _tstoi(IniStr);
		}
		if( ::GetPrivateProfileString( L"D3DSetting", L"multisampletype", L"0", IniStr, 256, L".\\D3DSetting.ini" ) )
		{
			switch( _tstoi(IniStr) )
			{
			case 0:
				*dwMultiSample = D3DMULTISAMPLE_NONE;
				break;
			case 2:
				*dwMultiSample = D3DMULTISAMPLE_2_SAMPLES;
				break;
			case 3:
				*dwMultiSample = D3DMULTISAMPLE_3_SAMPLES;
				break;
			case 4:
				*dwMultiSample = D3DMULTISAMPLE_4_SAMPLES;
				break;			
			case 5:
				*dwMultiSample = D3DMULTISAMPLE_5_SAMPLES;
				break;
			case 6:
				*dwMultiSample = D3DMULTISAMPLE_6_SAMPLES;
				break;
			case 7:
				*dwMultiSample = D3DMULTISAMPLE_7_SAMPLES;
				break;
			case 8:
				*dwMultiSample = D3DMULTISAMPLE_8_SAMPLES;
				break;
			default:
				*dwMultiSample = D3DMULTISAMPLE_NONE;
				break;
			}
		}
	}
	///////////////////////////////////////////////////////////////////
			

	OyDEVICEINFO_D3DDEVICE	*pDev = NULL;
	for(UINT nAdapter=0; nAdapter<m_dwNumAdapters; nAdapter++) 
	{
		for(UINT nDev=0; nDev<m_xAdapterInfo[nAdapter].nNumDevs; nDev++) 
		{
			if( pD->d3dDevType == m_xAdapterInfo[nAdapter].d3dDevs[nDev].d3dDevType )
			{
				pDev = &m_xAdapterInfo[nAdapter].d3dDevs[nDev];
				memcpy( pD, pDev, sizeof(OyDEVICEINFO_D3DDEVICE) );
				return TRUE;
			}
		}
	}
	return FALSE;
}

